package main

import (
	"fmt"
	"math/rand"
	"time"
)

/*
	在说“通道”的时候指的都是双向通道，即：既可以发也可以收的通道。
	单向通道就是，只能发不能收，或者只能收不能发的通道。一个通道是双向的，还是单向的是由它的类型字面量体现的。

	var uselessChan = make(chan<- int, 1)
	声明并初始化了一个名叫uselessChan的变量。这个变量的类型是chan<- int，容量是1。
	在关键字chan右边的那个<-，这表示了这个通道是单向的，并且只能发而不能收。
	类似的，如果这个操作符紧挨在chan的左边，那么就说明该通道只能收不能发。所以，前者可以被简称为发送通道，后者可以被简称为接收通道。

问题：单向通道有什么应用价值？

	单向通道最主要的用途就是约束其他代码的行为。

问题 1：select语句与通道怎样联用，应该注意些什么？
	select语句只能与通道联用，它一般由若干个分支组成。每次执行这种语句的时候，一般只有一个分支中的代码会被运行。

	select语句的分支分为两种，一种叫做候选分支，另一种叫做默认分支。
	候选分支总是以关键字case开头，后跟一个case表达式和一个冒号，然后可以从下一行开始写入当分支被选中时需要执行的语句。

	默认分支其实就是 default case，因为，当且仅当没有候选分支被选中时它才会被执行，所以它以关键字default开头并直接后跟一个冒号。
	同样的，可以在default:的下一行写入要执行的语句。

	由于select语句是专为通道而设计的，所以每个case表达式中都只能包含操作通道的表达式。

	在使用select语句的时候，首先需要注意下面几个事情。
		1.如果加入了默认分支，那么无论涉及通道操作的表达式是否有阻塞，select语句都不会被阻塞。
		如果那几个表达式都阻塞了，或者说都没有满足求值的条件，那么默认分支就会被选中并执行。

		2.如果没有加入默认分支，那么一旦所有的case表达式都没有满足求值条件，那么select语句就会被阻塞。
		直到至少有一个case表达式满足条件为止。

		3.通道关闭了，而直接从通道接收到一个其元素类型的零值。所以，在很多时候，需要通过接收表达式的第二个结果值来判断通道是否已经关闭。
		一旦发现某个通道关闭了，就应该及时地屏蔽掉对应的分支或者采取其他措施。这对于程序逻辑和程序性能都是有好处的。

		4.select语句只能对其中的每一个case表达式各求值一次。所以，如果想连续或定时地操作其中的通道的话，
		就往往需要通过在for语句中嵌入select语句的方式实现。但这时要注意，简单地在select语句的分支中使用break语句，
		只能结束当前的select语句的执行，而并不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。


问题 2：select语句的分支选择规则都有哪些？
	1.对于每一个case表达式，都至少会包含一个代表发送操作的发送表达式或者一个代表接收操作的接收表达式，同时也可能会包含其他的表达式。
	比如，如果case表达式是包含了接收表达式的短变量声明时，那么在赋值符号左边的就可以是一个或两个表达式，不过此处的表达式的结果必须是可以被赋值的。
	当这样的case表达式被求值时，它包含的多个表达式总会以从左到右的顺序被求值。

	2.select语句包含的候选分支中的case表达式都会在该语句执行开始时先被求值，并且求值的顺序是依从代码编写的顺序从上到下的。
	结合上一条规则，在select语句开始执行时，排在最上边的候选分支中最左边的表达式会最先被求值，然后是它右边的表达式。
	仅当最上边的候选分支中的所有表达式都被求值完毕后，从上边数第二个候选分支中的表达式才会被求值，
	顺序同样是从左到右，然后是第三个候选分支、第四个候选分支，以此类推。

	3.对于每一个case表达式，如果其中的发送表达式或者接收表达式在被求值时，相应的操作正处于阻塞状态，
	那么对该case表达式的求值就是不成功的。在这种情况下，可以说，这个case表达式所在的候选分支是不满足选择条件的。

	4.仅当select语句中的所有case表达式都被求值完毕后，它才会开始选择候选分支。这时候，它只会挑选满足选择条件的候选分支执行。
	如果所有的候选分支都不满足选择条件，那么默认分支就会被执行。如果这时没有默认分支，那么select语句就会立即进入阻塞状态，
	直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件，select语句（或者说它所在的 goroutine）就会被唤醒，
	这个候选分支就会被执行。

	5.如果select语句发现同时有多个候选分支满足选择条件，那么它就会用一种伪随机的算法在这些分支中选择一个并执行。
	注意，即使select语句是在被唤醒时发现的这种情况，也会这样做。

	6.一条select语句中只能够有一个默认分支。并且，默认分支只在无候选分支可选时才会被执行，这与它的编写位置无关。

	7.select语句的每次执行，包括case表达式求值和分支选择，都是独立的。
	不过，至于它的执行是否是并发安全的，就要看其中的case表达式以及分支中，是否包含并发不安全的代码了。


 */
func main() {

	//intChannel01 := make(chan <- int, 1)
	//SendInt(intChannel01)

	intChannel02 :=getIntChan()

	/*
		带有range子句的for语句
		这样一条for语句会不断地尝试从intChannel02种取出元素值，即使intChannel02被关闭，它也会在取出所有剩余的元素值之后再结束执行。
		当intChannel02中没有元素值时，它会被阻塞在有for关键字的那一行，直到有新的元素值可取。
		假设intChannel02的值为nil，那么它会被永远阻塞在有for关键字的那一行。
	 */
	for elem := range intChannel02 {
		fmt.Printf("The element in intChannel02: %v\n", elem)
	}

	/*
		初始化3个通道
	 */
	intChannels :=[3] chan int{
		make(chan int, 1),
		make(chan int, 1),
		make(chan int, 1),
	}

	// 随机选择一个通道，并向它发送元素值。
	index := rand.Intn(3)
	fmt.Printf("The index: %d\n", index)
	intChannels[index] <- index

	/*
		用一个包含了三个候选分支的select语句，分别尝试从上述三个通道中接收元素值，哪一个通道中有值，哪一个对应的候选分支就会被执行。
		后面还有一个默认分支，不过在这里它是不可能被选中的。
	*/
	select {
	case <- intChannels[0]:
		fmt.Println("The first candidate case is selected.")
	case <- intChannels[1]:
		fmt.Println("The second candidate case is selected.")
	case elem := <- intChannels[2]:
		fmt.Printf("The third candidate case is selected, the element is %d.\n", elem)
	default:
		fmt.Println("No candidate case is selected!")
	}

	/*
		并初始化了一个叫做intChannel03的通道。
	 */
	intChannel03 :=make(chan int, 1)
	/*
		通过time包中的AfterFunc函数约定在一秒钟之后关闭该通道。
	 */
	time.AfterFunc(time.Second, func() {
		close(intChannel03)
	})

	select {
	/*
		select语句只有一个候选分支，在其中利用接收表达式的第二个结果值对intChannel03通道是否已关闭做了判断，
		并在得到肯定结果后，通过break语句立即结束当前select语句的执行。
	 */
	case _, ok := <- intChannel03:
		if !ok {
			fmt.Println("The candidate case is closed.")
			break
		}
		fmt.Println("The candidate case is selected.")
	}

}

/*
	函数只接受一个chan<- int类型的参数。在这个函数中的代码只能向参数ch发送元素值，而不能从它那里接收元素值。这就起到了约束函数行为的作用。
 */
func SendInt(ch chan <- int)  {
	ch <- rand.Intn(1000)
}

/*
	Notifier接口中的SendInt方法只会接受一个发送通道作为参数，所以，在该接口的所有实现类型中的SendInt方法都会受到限制。
 */
type Notifier interface {
	SendInt(ch chan <- int)
}

/*
	还可以在函数声明的结果列表中使用单向通道。
 */
func getIntChan()  <- chan int{
	num :=5
	channel :=make(chan int,num)
	for i:=0; i< num; i++ {
		channel <- i
	}
	close(channel)
	return channel
}
